Skip to content

docs(docker): add nginx + PHP-FPM reference configuration to fix login redirect loop#8207

Merged
DawoudIO merged 3 commits intomasterfrom
copilot/fix-login-loop-churchcrm
Mar 7, 2026
Merged

docs(docker): add nginx + PHP-FPM reference configuration to fix login redirect loop#8207
DawoudIO merged 3 commits intomasterfrom
copilot/fix-login-loop-churchcrm

Conversation

Copy link
Contributor

Copilot AI commented Mar 6, 2026

ChurchCRM users deploying with nginx + PHP-FPM hit an infinite redirect loop (/session/begin → /session/begin) because nginx routes everything to the root index.php, which redirects unauthenticated users to /session/begin — which also hits index.php — and so on.

The loop happens because ChurchCRM has 9 independent Slim 4 entry points (one per subdirectory). Apache resolves this automatically via per-directory .htaccess mod_rewrite rules; nginx requires explicit location blocks per sub-app.

Changes

  • docker/nginx/default.conf — nginx server block with location ^~ blocks routing each sub-application prefix to its own index.php. Regex locations for static assets and PHP, security denies for /logs, /tmp_attach, and dotfiles. Commented subdirectory-install variant at the bottom.
# Each Slim sub-app gets its own routing block
location ^~ /session {
    try_files $uri /session/index.php$is_args$args;
    location ~ \.php$ {
        fastcgi_pass  php-fpm:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}
# ... repeated for /api, /v2, /admin, /finance, /kiosk, /plugins, /external, /setup
  • docker/Dockerfile.churchcrm-fpm-php8 — PHP-FPM multi-stage image (prod / dev) with all required extensions (bcmath, gd, intl, mysqli, pdo_mysql, sodium, zip, …). Mirrors the existing Dockerfile.churchcrm-apache-php8.

  • docker/docker-compose.nginx.yaml — Self-contained Compose reference (MariaDB + PHP-FPM + nginx). Not wired into CI; intended as a starting point for self-hosted deployments.

  • docker/README.md — Added intro table distinguishing dev/CI (Apache) from self-hosted (nginx), plus a new section explaining the routing architecture, the loop pitfall, quick-start, and customisation notes.

Original prompt

This section details on the original issue you should resolve

<issue_title>Question: Docker Installation Challenges</issue_title>
<issue_description>## How can we help?

Please share briefly:

  • What you're trying to do: I'm trying to install ChurchCRM into an environment that is otherwise manged through docker-compose. Since ChurchCRM doesn't have a dedicated set of docker containers, I've been attempting to install it using a combination of nginx, mariadb, and a custom PHP dockerfile with all the needed PHP extensions. I've made good progress (and I'm looking to share my procedures once I'm successful), but I'm running into an issue just a few feet from the finish line.

Specifically, I'm getting a login loop after finishing the initial setup wizard, 'too many redirects'.

From Claude:
This is the key finding! Look at what's happening:

✅ Cookie IS being set — CRM-xxxxx=yyy — so session saving is working now
✅ Only one redirect with curl+cookies — not an infinite loop
❌ It's redirecting /session/begin → /session/begin — redirecting to itself!

  • What you've tried: Steps or settings you've attempted
    This occurs after attempting to go to http://churchcrm.example.com, once the setup wizard has been completed.

  • Expected behavior: What you expected to happen
    Getting a login prompt =)

Add environment details only if relevant (version, hosting type, etc.).

My Docker Compose:


services:

  churchcrm-mariadb:
    image: mariadb:11
    container_name: churchcrm-mariadb
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 12345
      MYSQL_DATABASE: churchcrm
      MYSQL_USER: churchcrm
      MYSQL_PASSWORD: 12345
    volumes:
      - churchcrm-mariadb:/var/lib/mysql

  churchcrm-php:
    image: php84-custom1
    container_name: churchcrm-php
    restart: unless-stopped
    volumes:
      - churchcrm-php:/usr/local/etc/php/conf.d
      - churchcrm-www:/var/www/html
    depends_on:
      - churchcrm-mariadb
 
  churchcrm-nginx:
    image: nginx:stable-alpine
    container_name: churchcrm-nginx
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - churchcrm-www:/var/www/html
      - churchcrm-nginx:/etc/nginx/
    depends_on:
      - churchcrm-php

# =========================
  # PHPMYADMIN
  # =========================

  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    container_name: churchcrm-phpmyadmin
    ports:
      - "7381:80"
    environment:
      - PMA_HOST=churchcrm-mariadb
      - PMA_PORT=3306
      - TZ=America/New_York
    restart: ${RESTART_POLICY}
     
volumes:
  churchcrm-php:
  churchcrm-nginx:
  churchcrm-www:
  churchcrm-mariadb:

My Dockerfile for my php container:


RUN apt-get update && apt-get install -y \
    libzip-dev \
    libicu-dev \
    libpng-dev \
    libjpeg-dev \
    libfreetype6-dev \
    libxml2-dev \
    libonig-dev \
    unzip \
    git \
    curl \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install \
        pdo_mysql \
        mysqli \
        intl \
        gd \
        zip \
        mbstring \
        xml \
        opcache \
        gettext \
    && apt-get clean

My php override file:

session.save_path = /tmp
upload_max_filesize = 20M
post_max_size = 25M
memory_limit = 256M

My nginx config:

server {
    listen 80;
    server_name crm.example.org;
    root /var/www/html;
    index index.php index.html;
    client_max_body_size 100M;

    location ^~ /setup {
        try_files $uri $uri/ /setup/index.php$is_args$args;

        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass churchcrm-php:9000;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_index index.php;
        }
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass churchcrm-php:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_index index.php;
    }

    location ~ /\.ht {
        deny all;
    }
}

...I think that's everything =)</issue_description>

<agent_instructions>review the docker setup used for dev/ci/test and recommend a path forward </agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: DawoudIO <554959+DawoudIO@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix login loop issue after ChurchCRM setup docs(docker): add nginx + PHP-FPM reference configuration to fix login redirect loop Mar 6, 2026
@DawoudIO DawoudIO marked this pull request as ready for review March 7, 2026 15:55
@DawoudIO DawoudIO requested a review from a team as a code owner March 7, 2026 15:55
@DawoudIO DawoudIO requested review from DAcodedBEAT, DawoudIO, MrClever, bigtigerku, Copilot, grayeul and respencer and removed request for a team March 7, 2026 15:55
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a reference nginx + PHP-FPM Docker deployment and documentation to prevent the /session/begin → /session/begin redirect loop by routing each Slim sub-app prefix to its own index.php.

Changes:

  • Added nginx default.conf with explicit per-sub-app location ^~ routing and basic hardening rules.
  • Added PHP-FPM multi-stage Dockerfile and a standalone docker-compose.nginx.yaml reference stack.
  • Expanded docker/README.md with nginx routing explanation, quick start, and customization notes.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
docker/nginx/default.conf Adds nginx routing blocks per Slim sub-app to avoid redirect loop; adds deny rules and static caching.
docker/docker-compose.nginx.yaml Provides a reference Compose stack for MariaDB + PHP-FPM + nginx using the new config.
docker/Dockerfile.churchcrm-fpm-php8 Introduces a PHP-FPM image (prod/dev stages) with required PHP extensions and dev tooling.
docker/README.md Documents dev/CI (Apache) vs self-hosted (nginx) paths, routing rationale, and quick start.

Comment on lines +6 to +9
| Configuration | Use Case |
|---------------|----------|
| `docker-compose.yaml` | Development and testing (Apache + PHP). Used by the automated test suite and local development. |
| `docker-compose.nginx.yaml` | Self-hosted or production reference (nginx + PHP-FPM). Starting point for your own deployment. |
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These markdown tables use double leading pipes (|| ... | ... |), which GitHub Markdown renders as extra empty columns. Use standard table syntax with single leading/trailing pipes (e.g., | Configuration | Use Case |) throughout the README (including the URL prefix table).

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +42
location ~ /\. {
deny all;
return 404;
}
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block both deny all and return 404; in nginx the access phase will typically produce a 403 before return 404 is applied, so the intent to hide dotfiles may not work. Also, this pattern will block /.well-known/* (commonly needed for ACME/Let’s Encrypt HTTP-01 challenges). Consider switching to a pure return 404 (without deny) and/or exempting /.well-known/ via a more specific allow rule before this location.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +53
xml \
zip

# Sodium is bundled with PHP 7.2+ but needs to be enabled
RUN docker-php-ext-enable sodium

Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker-php-ext-enable sodium can fail on official PHP images when sodium is compiled in (no sodium.so to enable). To make the build reliable, either explicitly install it as a shared extension (docker-php-ext-install sodium) or drop this line if the base image already provides it enabled.

Suggested change
xml \
zip
# Sodium is bundled with PHP 7.2+ but needs to be enabled
RUN docker-php-ext-enable sodium
sodium \
xml \
zip

Copilot uses AI. Check for mistakes.
Comment on lines +82 to +88
# Install Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
rm composer-setup.php

# Install NVM + Node.js (needed to run npm run deploy inside the container)
RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh -o /opt/node-install.sh && \
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dev stage downloads and executes remote installer scripts (Composer + NVM) without integrity verification. Composer recommends verifying the installer signature; similarly, consider pinning to a commit SHA and/or verifying a checksum/signature for the NVM install script to reduce supply-chain risk.

Suggested change
# Install Composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
rm composer-setup.php
# Install NVM + Node.js (needed to run npm run deploy inside the container)
RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh -o /opt/node-install.sh && \
# Install Composer (with installer signature verification)
RUN EXPECTED_SIGNATURE="$(curl -s https://composer.github.io/installer.sig)" && \
php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \
ACTUAL_SIGNATURE="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" && \
if [ "$EXPECTED_SIGNATURE" != "$ACTUAL_SIGNATURE" ]; then >&2 echo 'ERROR: Invalid Composer installer signature'; rm composer-setup.php; exit 1; fi && \
php composer-setup.php --install-dir=/usr/local/bin --filename=composer && \
rm composer-setup.php
# Install NVM + Node.js (needed to run npm run deploy inside the container)
RUN curl https://raw.githubusercontent.com/nvm-sh/nvm/b3f16ba1f5a1ab3d6a0eef8949b0f9b867951376/install.sh -o /opt/node-install.sh && \

Copilot uses AI. Check for mistakes.
root /var/www/html;
index index.php;

client_max_body_size 100M;
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sets a 100M request body limit, but the PHP-FPM image config sets upload_max_filesize/post_max_size to 2G. This mismatch can cause confusing upload failures at 100M. Consider aligning these limits (or documenting why they differ) so nginx and PHP enforce consistent maxima.

Copilot uses AI. Check for mistakes.
@DawoudIO DawoudIO merged commit 6e46512 into master Mar 7, 2026
11 checks passed
@DawoudIO DawoudIO deleted the copilot/fix-login-loop-churchcrm branch March 7, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Question: Docker Installation Challenges

3 participants